import java.io.*;
import java.math.BigInteger;
import java.net.*;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

/**
 * Client Class
 * 
 * @author Ronald Brown
 * 
 */
public class Client implements Runnable {
	static Socket clientSocket = null;
	static PrintStream os = null;
	static DataInputStream is = null;
	static DataOutputStream dOS = null;
	static BufferedReader inputLine = null;
	static boolean closed = false;
	static Encryption Aencrypter;

	public static void main(String[] args) {
		BigInteger[] pg = null;
		byte[] apub = null;
		String apubstr = "";
		byte[] bpub = null;
		String bpubstr = null;
		DiffieHellman dha = new DiffieHellman();
		
		// The default port
		int port_number = 2323;
		// The default host
		String host = "localhost";

		//Get a different port and host
		if (args.length < 2) {
			System.out
					.println("Now using Chatter Client  \n" + "Now using host="
							+ host + ", port_number=" + port_number);
		} else {
			host = args[0];
			port_number = Integer.valueOf(args[1]).intValue();
		}

		try {
			//Initialize the Socket
			clientSocket = new Socket(host, port_number);
			//Initialize the input and output streams
			inputLine = new BufferedReader(new InputStreamReader(System.in));
			os = new PrintStream(clientSocket.getOutputStream());
			is = new DataInputStream(clientSocket.getInputStream());
			dOS = new DataOutputStream(clientSocket.getOutputStream());
			
			//Start Diffie Hellman
			
			//Generate a p and a g
			pg = dha.GeneratePG();
			//Generate a public key for the client(a)
			apub = dha.CreateSendInteger(pg[0], pg[1], 16);

		} catch (UnknownHostException e) {
			System.err.println("Don't know about host " + host);
		} catch (IOException e) {
			System.err
					.println("Couldn't get I/O for the connection to the host "
							+ host);
		}

		// If everything has been initialized then we want to write some data
		// to the socket we have opened a connection to on port port_number

		if (clientSocket != null && os != null && is != null) {
			try {

				
				synchronized (clientSocket) {
					//Send the p to the server
					dOS.writeBytes(String.valueOf(pg[0]) + "\n");
				}
				synchronized (clientSocket) {
					//Send the g to the server
					dOS.writeBytes(String.valueOf(pg[1]) + "\n");
				}

				synchronized (clientSocket) {
					//Send the amount of bytes the server needs to read to get apub.
					dOS.writeBytes(String.valueOf(apub.length) + "\n");
				}
				
				try {
					//Wait on the server
					Thread.sleep(1000l);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				synchronized (clientSocket) {
					//Send the public key to the server.
					dOS.write(apub, 0, apub.length);
				}
				
				//Holds the number of bytes that the client needs to read from the server.
				int bytsToRead = 0;
				
				synchronized (clientSocket) {
					//Get the amount of stuff we need to read.
					bytsToRead = is.readInt();
				}
				
				//Create the holder for the servers public key
				bpub = new byte[bytsToRead];
				
				synchronized (clientSocket) {
					//Read the servers public key
					is.read(bpub, 0, bpub.length);
				}
				
				//Create the fulll key using the servers public key.
				SecretKey Ka = dha.CreateFullKey(bpub);
				
				//create an encrypter using the full key.
				Aencrypter = new Encryption(Ka);
				
				// Create a thread to read from the server
				new Thread(new Client()).start();

				synchronized (clientSocket) {
					while (!closed) {
						//Take input from the user, encrypt it, and send it to the server.
						os.println(Aencrypter.encrypt(inputLine.readLine()));
					}
				}
				
				//Cleanup
				
				//Close the streams
				os.close();
				is.close();
				//Close the socket
				clientSocket.close();
			} catch (IOException e) {
				System.err.println("IOException:  " + e);
			}
		}
	}

	public void run() {
		String responseLine;
		
		//This thread will just keep reading from the server and outputting to the screen.
		
		try {
			while (true) {
				//Read from the server, and store into responseLine.
				try {
				responseLine = EncComm.recieve(is, Aencrypter);
				
				//Print out the response
				if(responseLine != null)
					System.out.println(responseLine);
				//Check if the user has disconnected, and break if it has.
				if (responseLine.indexOf("Disconnected") != -1)
					break;
				}
				catch (Exception ex) {
					System.out.println("Exiting...");
					System.exit(0);
				}
			}
			
			//Assert an end for the main thread.
			closed = true;
		} catch (Exception e) {
			System.err.println("IOException:  " + e);
		}
	}
}